/*
 * CompresorAritmetico.cpp
 *
 *  Created on: 26/06/2012
 *      Author: natuchis
 */

#include "CompresorAritmetico.h"

#define PRECISION 30
#define CANTIDADCARACTERES 257

CompresorAritmetico::CompresorAritmetico(const char* nombreArchivo, char indicador) {
	this->pisoactual = 0;
	this->techoactual = (pow (2.0,PRECISION))-1;
	this->UF = 0;

	this->bufferEscritura = new char [8];
	this->bitsEnBuffer = 0;
	if (indicador == 'c') {
		string nombre = string(nombreArchivo);
		nombre += ".neu";
		this->archivo = fopen(nombre.c_str(), "a+b");
		this->nombreArchivo = string(nombre);
	}
	else {
		this->archivo = fopen(nombreArchivo,"a+b");
		this->nombreArchivo = string(nombreArchivo);
	}
	this->cantidadBits = 0;
	this->bufferLectura = new char [40];
	this->posicionArchivo = 0;
}

CompresorAritmetico::~CompresorAritmetico() {
	fclose(this->archivo);
	//Liberar memoria de los char
	delete [] this->bufferEscritura;
	delete [] this->bufferLectura;
}

void CompresorAritmetico::comprimirCaracter (Datos &datos) {

	string caracter = datos.getCaracAComp();
	unsigned long int denominador = datos.getFrecTotal();


	double piso=this->pisoactual;
	double techo=this->pisoactual;
	int UFanterior=0;

	double intervalo = (this->techoactual-this->pisoactual+1) / denominador;
	map<string,Caracter> mapa = datos.getCaracteres();
	map<string,Caracter>::iterator iterador = mapa.begin();


	while((iterador != mapa.end()) && (iterador->first <= caracter)) {
		piso = techo+1;
		techo = piso + intervalo * iterador->second.getFrecuencia() -1;
		iterador++;
	}

	piso--;
	if (techo != this->techoactual) techo--;
	char* pisob = decABin(piso,PRECISION);
	char* techob = decABin(techo,PRECISION);

	char* aEmitir = normalizar(pisob, techob);

	this->pisoactual = binADec(pisob, PRECISION);
	this->techoactual = binADec(techob, PRECISION);

	//Libero la memoria dinamica reservada en decABin
	delete [] pisob;
	delete [] techob;

	if (caracter == "EOF") emitirEOF(aEmitir,UFanterior);
	else emitir(aEmitir);

	delete [] aEmitir;
}

string CompresorAritmetico::descomprimirCaracter (Datos &datos) {
	char* valorEmitido = obtenerCadena(this->cantidadBits);
	string valor = "";

	unsigned long int denominador = datos.getFrecTotal();

	double intervalo=0;
	double piso=this->pisoactual;
	double techo=this->pisoactual;


	intervalo = (this->techoactual-this->pisoactual+1) / denominador;

	map<string,Caracter> mapa = datos.getCaracteres();
	map<string,Caracter>::iterator iterador = mapa.begin();

	while((iterador != mapa.end()) && (techo-1 < binADec(valorEmitido,PRECISION))) {
		piso = techo+1;
		techo = piso + intervalo * iterador->second.getFrecuencia() - 1;
		valor = iterador->first;
		iterador++;
	}

	//Libero la memoria dinamica reservada en obtenerCadena
	delete [] valorEmitido;

	piso--;
	if (techo != this->techoactual) techo--;

	char* pisob = decABin(piso,PRECISION);
	char* techob = decABin(techo,PRECISION);
	char* auxiliar = normalizar(pisob,techob);

	//Ahora que normalice libero la memoria dinamica que este devuelve
	delete [] auxiliar;

	this->pisoactual = binADec(pisob, PRECISION);
	this->techoactual = binADec(techob, PRECISION);

	//Libero la memoria dinamica reservada en decABin
	delete [] pisob;
	delete [] techob;
	return valor;
}

string CompresorAritmetico::getRuta() {

	this->nombreArchivo.erase(this->nombreArchivo.size()-4,4);
	return string(this->nombreArchivo);
}

char* CompresorAritmetico::obtenerCadena(int numero) {
	Util* util = new Util();
	unsigned char aux;
	char* binario;// = new char[8];

	if (this->posicionArchivo != 0) {
		if (numero != 0) {
			for (int x=0; x<40-numero; x++) {
				this->bufferLectura[x] = this->bufferLectura[x+numero];
			}
			this->bitsEnBuffer = this->bitsEnBuffer - numero;
		}
	}
	else {
		for (int x=0; x<5; x++) {
			util->desSerializar(this->archivo, aux, this->posicionArchivo);
			this->posicionArchivo++;
			int decimal = aux;
			binario = decABin(decimal,8);
			for (int y=0; y<8; y++) {
				this->bufferLectura[this->bitsEnBuffer]=binario[y];
				this->bitsEnBuffer++;
			}
			delete []binario;
		}
	}
	int bits = this->bitsEnBuffer;
	if (this->UF != 0) {
		for (int x=1; x<=bits - this->UF; x++) {
			this->bufferLectura[x] = this->bufferLectura[x+this->UF];
		}
		this->bitsEnBuffer=this->bitsEnBuffer-this->UF;
	}
	this->UF=0;
	while (this->bitsEnBuffer < PRECISION) {
		util->desSerializar(this->archivo, aux,this->posicionArchivo);
		this->posicionArchivo++;
		int decimal = aux;
		binario = decABin(decimal,8);
		for (int y=0; y<8; y++) {
			this->bufferLectura[this->bitsEnBuffer]=binario[y];
			this->bitsEnBuffer++;
		}
		delete []binario;
	}

	//Libero la memoria dinÃ¡mica
//	delete [] binario;

	char* resultado = new char [PRECISION+1];
	resultado[PRECISION]=0;

	for (int x=0; x<PRECISION; x++) {
		resultado [x] = this->bufferLectura[x];
	}
	delete util;
	return resultado;
}

char* CompresorAritmetico::normalizar(char* pisob, char* techob) {
	char* aemitir;
	short i=0;

	while (pisob[i]==techob[i]) {
		i++;
	}

	this->cantidadBits=i; //Se utiliza para descomprimir

	if (i!=0) {
		aemitir = new char [i+this->UF+1];
		aemitir[i+this->UF] = 0;
		i=0;
		while (pisob[i]==techob[i]) {
			if (i==0) {
				aemitir[i]=pisob[i];
				for (int x=1; x<=this->UF; x++) {
					if (aemitir[i] == '1') aemitir[i+x]='0';
					else aemitir[i+x]='1';
				}
				i++;
			}
			else {
				aemitir[i + this->UF]=pisob[i];
				i++;
			}
		}

		this->UF=0;

		short aux = 0;
		if (i <= PRECISION) {
			pisob[aux]=pisob[i];
			techob[aux]=techob[i];
			i++;
			aux++;
		}

		bool bandera = false;
		while ((i<PRECISION) && (! bandera)) {
			if ((pisob[i]=='1') && (techob[i]=='0')) {
				this->UF++;
				i++;
			}
			else {
				bandera=true;
			}
		}

		while (i<PRECISION) {
			pisob[aux]=pisob[i];
			techob[aux]=techob[i];
			aux++;
			i++;
		}

		while (aux<PRECISION) {
			pisob[aux]='0';
			techob[aux]='1';
			aux++;
		}
	}
	else {
		aemitir = new char [1];
		aemitir[0] = 0;
		//Debo verificar el underflow...
		int i = 1;
		while( (pisob[i] == '1') && (techob[i] == '0') ) {
			this->UF++;
			i++;
		}

		//Ahora que ya se cuantos bits de underflow tengo, realizo los corrimientos
		short aux = 1;
		while (i<PRECISION) {
			pisob[aux]=pisob[i];
			techob[aux]=techob[i];
			aux++;
			i++;
		}

		//Ahora realizo el relleno
		while (aux<PRECISION) {
			pisob[aux]='0';
			techob[aux]='1';
			aux++;
		}
	}

	return aemitir;
}


char* CompresorAritmetico::decABin(int numero, short cantidadBits) {
	int base=2;
	int i=0;
	int resto;
	char* binario = new char[cantidadBits+1];
	while (numero >= 1) {
		resto = numero % base;
		numero /=  base;
		if (resto==0) {
			binario[cantidadBits-1-i]='0';}
		else {
			binario[cantidadBits-1-i]='1';}
		i++;
	}
	while (i<cantidadBits) {
		binario[cantidadBits-1-i]='0';
		i++;
	}
	binario[cantidadBits]=0;
	return binario;
}

int CompresorAritmetico::binADec(char* binario, short int tamanioCadena) {
	int numero = 0;
	for (int i=0; i<tamanioCadena; i++) {
		if (binario[i]=='1') numero += pow(2.0,tamanioCadena-1-i);
	}
	return numero;
}

void CompresorAritmetico::emitir (char* auxiliar) {
	unsigned int i=0;

	while (i<strlen(auxiliar)) {
		if (this->bitsEnBuffer < 8) {
			this->bufferEscritura[this->bitsEnBuffer] = auxiliar[i];
			i++;
			this->bitsEnBuffer++;
		}
		else {
			unsigned char aescribir = (binADec(this->bufferEscritura,8));
			fputc (aescribir , this->archivo );
			this->bitsEnBuffer=0;
		}
	}

	if (this->bitsEnBuffer==8) {
		unsigned char aescribir = (binADec(this->bufferEscritura,8));
		fputc (aescribir , this->archivo);
		this->bitsEnBuffer=0;
	}

}

//Devuelve TRUE si la primer cadena es menor estricto que la segunda
bool CompresorAritmetico::compararCadenas(char* primerCadena, char* segundaCadena, int longitud) {
	int i=0;
	int aux=0;
	/*	aux = 0 primerCadena = segundaCadena
	 *  aux = 1 primerCadena < segundaCadena
	 * 	aux = 2 primerCadena > segundaCadena
	 */
	while ( (i<longitud) && (aux == 0)) {
		if (primerCadena[i] < segundaCadena[i]) aux=1;
		else if (primerCadena[i] > segundaCadena[i]) aux=2;
	}

	if (aux == 1) return true;
	else return false;
}

//Tengo que emitir el Overflow seguido de un 1 y dsp completar con 0s hasta llegar a 32 bits
void CompresorAritmetico::emitirEOF(char*aEmitir, int UFanterior) {

	char* pisob = decABin (this->pisoactual, PRECISION);
	char* auxiliar = new char[PRECISION+UFanterior+1];

	auxiliar[PRECISION+UFanterior]=0;
	unsigned int x;
	int aux=0;
	for (x=0; x<strlen(aEmitir); x++) {
		auxiliar[aux] = aEmitir[x];
		aux++;
	}
	for (x=0; x < (strlen(pisob) - strlen(aEmitir)); x++) {
		if (x!=0) {
			auxiliar[aux]='0';
		}
		else auxiliar[aux] = '1';

		aux++;
	}

	unsigned int i=0;
	while (i < strlen(auxiliar)) {
		if (this->bitsEnBuffer < 8) {
			this->bufferEscritura[this->bitsEnBuffer] = auxiliar[i];
			i++;
			this->bitsEnBuffer++;
		}
		else {
			unsigned char aescribir = (binADec(this->bufferEscritura,8));
			fputc (aescribir , this->archivo );
			this->bitsEnBuffer=0;
		}
	}

	i=0;
	if (this->bitsEnBuffer==8) {
		unsigned char aescribir = (binADec(this->bufferEscritura,8));
		fputc (aescribir , this->archivo );
		this->bitsEnBuffer=0;
	}
	else {
		if (this->bitsEnBuffer > 0) {
			while (this->bitsEnBuffer < 8) {
				this->bufferEscritura[this->bitsEnBuffer] = '1';
				i++;
				this->bitsEnBuffer++;
			}
		}
	}

	if (i!=0) {
		unsigned char aescribir = (binADec(this->bufferEscritura,8));
		fputc (aescribir , this->archivo );
	}

	delete [] auxiliar;
	delete [] pisob;
}

void CompresorAritmetico::setOrdenCompresionMaximo(unsigned short int ordenMaximo) {
	//Debo escribir en las dos primeras posiciones del archivo el orden de compresion
	Util util;
	util.serializar(this->archivo,ordenMaximo);
	//Avanzo la posicion en el archivo
	this->posicionArchivo += sizeof(ordenMaximo);
}

unsigned short int CompresorAritmetico::getOrdenCompresionMaximo() {
	//Debo levantar del archivo comprimido el orden de compresion utilizado. Este se encuentra al principio del mismo.
	unsigned short int orden =0;
	Util util;
	util.desSerializar(this->archivo,orden);
	//Avanzo la posicion el archivo;
	this->posicionArchivo += sizeof(orden);
	return orden;
}
